//koa 源码自撸
const Emitter = require('events');
const http = require('http');
//严格来说，context 就是语法糖，也就包含了 request、response，
var context = require('./context');
const compose = require('./compose');

module.exports = class Application extends Emitter{
    constructor(){
        super();
        this.middleware = [];
        this.context = {};
    }

    use(fn){
        //非函数则抛出异常
        if(typeof fn!=='function'){
            throw new TypeError('middleware must be a function');
        }

        this.middleware.push(fn);
        return this;
    }

    listen(...args){
        let server = http.createServer(this.callback());
        return server.listen(...args);
    }

    //这里需要处理所有请求，中间件的逻辑就在这里
    callback(){
        //compose 的第二个参数并没有传递进来，返回了一个函数，其参数为 ctx
        let fn = compose(this.middleware);
        let handlRequest = (req,res)=>{
            let ctx = this.createContext(req, res);
            return this.handlRequest(ctx,fn);
        }

        return handlRequest;
    }

    createContext(req,res){
        //**** 以下这些逻辑在原框架内比较复杂，实际上 ctx 就是融合了 3个对象： app、req、res */
        //根据 createServer 传入的 req 和 res 来构建 ctx 这个上下文
        // this.context 从哪里来
        // this.request 和 this.response 是在哪里定义的
        let context = Object.create(this.context);
        // let request = context.request = Object.create(this.request);
        // let response = context.response = Object.create(this.response);
        // context.app = request.app = response.app = this;
        // context.req = request.req = response.req = req;
        // context.res = request.res = response.res = res;
        // request.ctx = response.ctx = context;
        // request.response = response;
        // response.request = request;
        // context.originalUrl = request.originalUrl = req.url;
        // context.state = {};
        context.app = this;
        context.req = req;
        context.res = res;
        return context;
    }

    handlRequest(ctx, composefn){
        //所有中间件执行完毕，调用 respond
        let handleResponse = ()=> this.respond(ctx);
        //composefn 内部最后一次执行返回的是 Promise.resolve()，所以这样写确实没问题
        //promise 内部又调用了 n次 promise
        return composefn(ctx).then(handleResponse).catch(err=>{
            console.log('Something is wrong', err);
        });
    }

    respond(ctx){
        //取出 res
        let res = ctx.res;
        let body = ctx.body;
        return res.end(body);
    }
}